﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.CodeDom;
using OrganizationName.CodeGeneration;
using System.Collections;

namespace OrganizationName.Factory
{
    public class AnonymousInstance : GenericMaterial
    {
        public virtual LifeTimeContract LifeTimeContract { get; set; }
        public virtual Parameter[] Parameter { get; set; }
        public virtual Attribute[] Attribute { get; set; }
        public virtual Literal Literal { get; set; }
        public virtual Element[] Element { get; set; }
        public virtual string Reference { get; set; }
        public virtual Invoke Invoke { get; set; }

        public virtual string MethodName
        {
            get
            {
                return "_GET" + UniqueId;
            }
        }

        internal static CodeExpression ToRightHandSideWrapperMethodCallExpression(ICodeContext codeContext, AnonymousInstance anonymousInstance)
        {
            return new CodeMethodInvokeExpression
            (
                new CodeMethodReferenceExpression
                (
                    null,
                    anonymousInstance.MethodName
                ),
                new CodeExpression[] { }
            );
        }

        public bool IsVoid(ICodeContext codeContext)
        {
            return GetConcreteType(codeContext) == typeof(void);
        }

        public bool IsFirstLevelInstance()
        {
            return Parent is RuntimeConfiguration;
        }

        protected virtual void WrapAsMethod(ICodeContext codeContext)
        {
            CodeMemberMethod instanceMethod = new CodeMemberMethod();
            instanceMethod.Name = MethodName;
            instanceMethod.ReturnType = new CodeTypeReference(GetConcreteType(codeContext));
            instanceMethod.Statements.AddRange(codeContext.Statements);
            codeContext.Class.Members.Add(instanceMethod);
        }

        protected virtual CodeExpression GenerateChildInstance(ICodeContext codeContext, AnonymousInstance childInstance)
        {
            ICodeContext childContext = codeContext.CreateChild(childInstance);
            childInstance.GenerateCode(childContext);
            return new CodeVariableReferenceExpression(childInstance.UniqueId);
        }

        protected virtual CodeExpression[] GenerateChildInstances(ICodeContext codeContext, IEnumerable instanceCollection)
        {
            List<CodeExpression> instanceCodeExpressions = new List<CodeExpression>();
            if (instanceCollection != null)
            {
                foreach (AnonymousInstance childInstance in instanceCollection)
                {
                    instanceCodeExpressions.Add(GenerateChildInstance(codeContext, childInstance));
                }
            }
            return instanceCodeExpressions.ToArray();
        }

        public override void DeclarationPhase(ICodeContext codeContext)
        {
            if (!IsVoid(codeContext))
            {
                codeContext.Statements.Add(new CodeVariableDeclarationStatement(GetConcreteType(codeContext), UniqueId));
            }
            if (LifeTimeContract != null)
            {
                LifeTimeContract.DeclarationPhase(codeContext.CreateChild(LifeTimeContract));
            }
        }

        public override void InitializationPhase(ICodeContext codeContext)
        {
            System.Type concreteType = GetConcreteType(codeContext);
            CodeExpression valueToAssignExpression = null;

            string contextKey = ContextKey;
            bool isVoid = IsVoid(codeContext);
            bool isAccessible = !string.IsNullOrEmpty(contextKey);

            //if (isAccessible && !isVoid)
            //{
            //    codeContext.Statements.Add
            //    (
            //        new CodeConditionStatement
            //        (
            //            new CodeBinaryOperatorExpression
            //            (
            //                RuntimeContext.CompileTimeGet(Name),
            //                CodeBinaryOperatorType.IdentityInequality,
            //                new CodePrimitiveExpression(null)
            //            ),
            //            new CodeAssignStatement
            //            (
            //                new CodeVariableReferenceExpression(UniqueId),
            //                RuntimeContext.CompileTimeGet(Name, GetConcreteType(codeContext))
            //            )
            //        )
            //    );
            //    codeContext.Statements.Add(new CodeSnippetExpression(" else {"));
            //}

            if (Invoke != null)
            {
                Invoke.GenerateCode(codeContext.CreateChild(Invoke));
                valueToAssignExpression = Invoke.ToRightHandSideExpression(codeContext.CreateChild(Invoke));

            }
            else if (!string.IsNullOrEmpty(Reference))
            {
                AnonymousInstance referencedInstance = (codeContext.RootCodeProvider as RuntimeConfiguration).GetInstanceByName(Reference);

                if (referencedInstance == null)
                {
                    throw new CodeGenerationException("Referenced entity not found: " + Reference, codeContext, null);
                }

                valueToAssignExpression = referencedInstance.ToRightHandSideExpression(codeContext.RootContext.CreateChild(referencedInstance));
            }
            else if (Literal != null)
            {
                valueToAssignExpression = Literal.ToRightHandSideExpression(codeContext);
            }
            else if (concreteType.IsArray)
            {
                valueToAssignExpression = new CodeArrayCreateExpression(concreteType.GetElementType(), Element != null ? Element.Length : 0);
            }
            else if (concreteType.IsPrimitive)
            {
                valueToAssignExpression = new CodePrimitiveExpression(0);
            }
            else if (concreteType == typeof(string))
            {
                valueToAssignExpression = new CodePrimitiveExpression("");
            }
            else
            {
                CodeExpression[] childConstructors = GenerateChildInstances(codeContext, Parameter);
                valueToAssignExpression = new CodeObjectCreateExpression(GetConcreteType(codeContext), childConstructors);
            }

            if (!isVoid)
            {
                codeContext.Statements.Add(new CodeAssignStatement(new CodeVariableReferenceExpression(UniqueId), valueToAssignExpression));
            }

            //if (isAccessible && !isVoid)
            //{
            //    codeContext.Statements.Add(new CodeSnippetExpression("}"));
            //}
        }

        public override void FinalizationPhase(ICodeContext codeContext)
        {
            CodeExpression[] childElements = GenerateChildInstances(codeContext, Element);
            CodeExpression[] childAttributes = GenerateChildInstances(codeContext, Attribute);
            if (LifeTimeContract != null)
            {
                ICodeContext LifeTimeContractContext = codeContext.CreateChild(LifeTimeContract);
                LifeTimeContract.FinalizationPhase(LifeTimeContractContext);
            }

            if (!IsVoid(codeContext) && IsFirstLevelInstance())
            {
                codeContext.Statements.Add(new CodeMethodReturnStatement(new CodeVariableReferenceExpression(UniqueId)));
            }

            if (IsFirstLevelInstance())
            {
                WrapAsMethod(codeContext);
            }

            codeContext.AddReference(GetAbstractType(codeContext));
            codeContext.AddReference(GetConcreteType(codeContext));
        }

        public override System.CodeDom.CodeExpression ToRightHandSideExpression(ICodeContext codeContext)
        {
            if (IsFirstLevelInstance())
            {
                return ToRightHandSideWrapperMethodCallExpression(codeContext, this);
            }
            else
            {
                return new CodeVariableReferenceExpression(UniqueId);
            }
        }

        public override System.Type GetAbstractType(ICodeContext codeContext)
        {
            if (!string.IsNullOrEmpty(Reference))
            {
                return (codeContext.RootCodeProvider as RuntimeConfiguration).GetInstanceByName(Reference).GetAbstractType(codeContext);
            }
            if (!string.IsNullOrEmpty(Base))
            {
                return (codeContext.RootCodeProvider as RuntimeConfiguration).GetTypeByName(Base).GetConcreteType(codeContext);
            }
            else
            {
                return GetConcreteType(codeContext);
            }
        }

        public override System.Type GetConcreteType(ICodeContext codeContext)
        {
            if (!string.IsNullOrEmpty(Reference))
            {
                return (codeContext.RootCodeProvider as RuntimeConfiguration).GetInstanceByName(Reference).GetConcreteType(codeContext);
            }

            if (!string.IsNullOrEmpty(Impl))
            {
                Type implementedType = (codeContext.RootCodeProvider as RuntimeConfiguration).GetTypeByName(Impl);
                if (implementedType == null)
                {
                    throw new CodeGenerationException("The implemented type [" + Impl + "] could not be found", codeContext, null);
                }
                return implementedType.GetConcreteType(codeContext);
            }

            if (string.IsNullOrEmpty(Impl) && Invoke != null)
            {
                if (Invoke != null)
                {
                    return Invoke.GetConcreteType(codeContext.CreateChild(Invoke));
                }
            }

            if (!string.IsNullOrEmpty(Base))
            {
                return (codeContext.RootCodeProvider as RuntimeConfiguration).GetInstanceByBaseTypeName(Base).GetConcreteType(codeContext);
            }

            if (Invoke != null)
            {
                throw new CodeGenerationException("Concrete type could not be determined because \"Impl\" attribute is null or empty and none of the \"Invoke\" commands have an \"IsResult\" attribute set to \"true\".", codeContext, null);
            }
            else
            {
                throw new CodeGenerationException("Concrete type could not be determined because \"Impl\" attribute is null or empty", codeContext, null);
            }
        }
    }
}
